#include <utils/io.h>
#include <utils/string.h>

uint16 *videomem = (uint16 *)0xB8000;
uint32 cursor_x = 0, cursor_y = 0;
uint8 colors = 0x0F; // white on black

void scroll() {
    uint16 blank = ' ' | (colors << 8);
    if(cursor_y >= 25){
        uint16 line = cursor_y - 25 + 1;
        memcpy(videomem, videomem + line * 80, (25 - line) * 80 * 2);
        memsetw(videomem + (25 - line) * 80, blank, 80);
        cursor_y = 25 - 1;
    }
}

void move_cursor(uint32 newx, uint32 newy) {
    cursor_x = newx;
    cursor_y = newy;
    if(cursor_x >= 80)
    {
        cursor_x = 0;
        cursor_y++;
    }

    uint16 index = cursor_y * 80 + cursor_x;
    outb(0x3D4, 14);
    outb(0x3D5, index >> 8);
    outb(0x3D4, 15);
    outb(0x3D5, index);
}

void clear() {
    uint16 blank = ' ' | (colors << 8);
    uint16 i;
    for(i = 0;i < 25; i++){
        memsetw(videomem + i * 80, blank, 80);
    }
    move_cursor(0,0);
}

void kputc(uint8 c) {
    uint16 *where;
    
    switch(c){
        case 0x08:
            // backspace
            if(cursor_x) move_cursor(cursor_x-1, cursor_y);
            where = videomem + (cursor_y * 80 + cursor_x);
            *where = ' ' | (colors << 8);
            break;
        case 0x09:
            // tab
            move_cursor((cursor_x + 8) & ~(8 - 1), cursor_y);
            break;
        case 0x0D:
            // carriage return \r
            move_cursor(0, cursor_y);
            break;
        case 0x0A:
            // line feed \n
            move_cursor(0, cursor_y+1);
            break;
        default:
            if (c >= ' '){
                where = videomem + (cursor_y * 80 + cursor_x);
                *where = c | (colors << 8);
                move_cursor(cursor_x + 1, cursor_y);
            }
    }
#ifdef DEBUG_BOCHS    
    outb(0xE9, c);
    outb(0x37a, 0x04|0x08);
    outb(0x378, (unsigned char)c);
    outb(0x37a, 0x01);
#endif
    scroll();
}

void kputs(char *text) {
    char *p = text;
    while(*p){
        kputc(*p);
        p++;
    }
}

void kprintf (const char *format, ...) {
    char **arg = (char **) &format;
    char c;
    char buf[512];

    arg++;
     
    while ((c = *format++) != 0){
        if (c != '%'){
            kputc(c);
        } else {
            char *p;
     
            c = *format++;
            switch (c){
                case 'd':
                case 'x':
                    itoa (buf, c, *((int32 *) arg++));
                    kputs(buf);
                    break;
                case 's':
                    p = *arg++;
                    if (! p){
                        p = "(null)";
                    }
                    kputs(p);
                    break;
                default:
                    kputc(*((char *) arg++));
                    break;
            }
        }
    }
}

/* Sets the forecolor and backcolor that we will use */
void settextcolor(uint8 forecolor, uint8 backcolor) {
    colors = (backcolor << 4) | (forecolor & 0x0F);
}

void console_init(void) {
    clear();
}
